// Copyright Epic Games, Inc. All Rights Reserved.

#include "zencore/system.h"

#include <zencore/compactbinarybuilder.h>
#include <zencore/except.h>
#include <zencore/string.h>

#if ZEN_PLATFORM_WINDOWS
#	include <zencore/windows.h>

ZEN_THIRD_PARTY_INCLUDES_START
#	include <iphlpapi.h>
#	include <winsock2.h>
ZEN_THIRD_PARTY_INCLUDES_END
#elif ZEN_PLATFORM_LINUX
#	include <sys/utsname.h>
#elif ZEN_PLATFORM_MAC
#	include <unistd.h>
#endif

namespace zen {

using namespace std::literals;

int g_FakeCpuCount = 0;

void
SetCpuCountForReporting(int FakeCpuCount)
{
	g_FakeCpuCount = FakeCpuCount;
}

#if ZEN_PLATFORM_WINDOWS
std::string
GetMachineName()
{
	WCHAR NameBuffer[256];
	DWORD dwNameSize = 255;
	BOOL  Success	 = GetComputerNameW(NameBuffer, &dwNameSize);

	if (Success)
	{
		return WideToUtf8(NameBuffer);
	}

	return {};
}

SystemMetrics
GetSystemMetrics()
{
	SYSTEM_INFO SysInfo{};
	GetSystemInfo(&SysInfo);

	SystemMetrics Metrics;

	Metrics.LogicalProcessorCount = SysInfo.dwNumberOfProcessors;

	// Determine physical core count

	DWORD BufferSize = 0;
	BOOL  Result	 = GetLogicalProcessorInformation(nullptr, &BufferSize);
	if (int32_t Error = GetLastError(); Error != ERROR_INSUFFICIENT_BUFFER)
	{
		ThrowSystemError(Error, "Failed to get buffer size for logical processor information");
	}

	PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)Memory::Alloc(BufferSize);

	Result = GetLogicalProcessorInformation(Buffer, &BufferSize);
	if (!Result)
	{
		Memory::Free(Buffer);
		throw std::runtime_error("Failed to get logical processor information");
	}

	DWORD ProcessorPkgCount	 = 0;
	DWORD ProcessorCoreCount = 0;
	DWORD ByteOffset		 = 0;
	while (ByteOffset + sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION) <= BufferSize)
	{
		const SYSTEM_LOGICAL_PROCESSOR_INFORMATION& Slpi = Buffer[ByteOffset / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)];
		if (Slpi.Relationship == RelationProcessorCore)
		{
			ProcessorCoreCount++;
		}
		else if (Slpi.Relationship == RelationProcessorPackage)
		{
			ProcessorPkgCount++;
		}
		ByteOffset += sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
	}

	Metrics.CoreCount = ProcessorCoreCount;
	Metrics.CpuCount  = ProcessorPkgCount;

	Memory::Free(Buffer);

	// Query memory status

	MEMORYSTATUSEX MemStatus{.dwLength = sizeof(MEMORYSTATUSEX)};
	GlobalMemoryStatusEx(&MemStatus);

	Metrics.SystemMemoryMiB		  = MemStatus.ullTotalPhys / 1024 / 1024;
	Metrics.AvailSystemMemoryMiB  = MemStatus.ullAvailPhys / 1024 / 1024;
	Metrics.VirtualMemoryMiB	  = MemStatus.ullTotalVirtual / 1024 / 1024;
	Metrics.AvailVirtualMemoryMiB = MemStatus.ullAvailVirtual / 1024 / 1024;
	Metrics.PageFileMiB			  = MemStatus.ullTotalPageFile / 1024 / 1024;
	Metrics.AvailPageFileMiB	  = MemStatus.ullAvailPageFile / 1024 / 1024;

	return Metrics;
}
#elif ZEN_PLATFORM_LINUX
std::string
GetMachineName()
{
	static char Result[256] = "";
	if (!Result[0])
	{
		struct utsname name;
		const char*	   SysName = name.nodename;
		if (uname(&name))
		{
			SysName = "Unix Computer";
		}

		strcpy(Result, SysName);
	}
	return Result;
}

SystemMetrics
GetSystemMetrics()
{
	return {};
}
#elif ZEN_PLATFORM_MAC
std::string
GetMachineName()
{
	static char Result[256] = "";

	if (!Result[0])
	{
		gethostname(Result, sizeof(Result));
	}
	return Result;
}

SystemMetrics
GetSystemMetrics()
{
	return {};
}
#else
#	error "Unknown platform"
#endif

SystemMetrics
GetSystemMetricsForReporting()
{
	SystemMetrics Sm = GetSystemMetrics();

	if (g_FakeCpuCount)
	{
		Sm.CoreCount			 = g_FakeCpuCount;
		Sm.LogicalProcessorCount = g_FakeCpuCount;
	}

	return Sm;
}

std::string_view
GetOperatingSystemName()
{
	return ZEN_PLATFORM_NAME;
}

std::string_view
GetCpuName()
{
#if ZEN_ARCH_X64
	return "x64"sv;
#elif ZEN_ARCH_ARM64
	return "arm64"sv;
#endif
}

void
Describe(const SystemMetrics& Metrics, CbWriter& Writer)
{
	Writer << "cpu_count" << Metrics.CpuCount << "core_count" << Metrics.CoreCount << "lp_count" << Metrics.LogicalProcessorCount
		   << "total_memory_mb" << Metrics.SystemMemoryMiB << "avail_memory_mb" << Metrics.AvailSystemMemoryMiB << "total_virtual_mb"
		   << Metrics.VirtualMemoryMiB << "avail_virtual_mb" << Metrics.AvailVirtualMemoryMiB << "total_pagefile_mb" << Metrics.PageFileMiB
		   << "avail_pagefile_mb" << Metrics.AvailPageFileMiB;
}

}  // namespace zen
